; 1 Procedures as Argument
; we can make a "procedure factory"
(define (sum term next a b)
    (if (> a b)
        0
        (+ (term a) (sum term next (next a) b))
    )
)
(define (sum_fac term next) 
    (lambda (a b) 
        (sum term next a b)
    )
)

(define (inc x) (+ 1 x))
(define (identity x) x)
(define (cube x) (* x x x))

(define sum_int (sum_fac identity inc))
(sum_int 1 100)
(define sum_cube  (sum_fac cube inc))
(sum_cube 1 10)
; use lambda to construct procedure!
(define pi_sum (sum_fac (lambda (x) (/ 1 (* x (+ x 2.)))) (lambda (x) (+ x 4))))
(* 8 (pi_sum 1 1000))

; trapzoid integral formula using sum
; I = dx * [f_1 + f_2 + ... + f_{N-1}], f_{N-1} = f(b-dx/2), f_1 = f(a+dx/2)
(define (trapzoid f a b dx)
   (* ((sum_fac f (lambda (x) (+ x dx))) (+ a (/ dx 2)) (- b (/ dx 2))) dx)
)
(trapzoid (lambda (x) (* x x x)) 0 1 0.1)
; simpson integral on exercise

; 2 Constructing Procedure using lambda
; and using let to create local var, which is just syntax sugar of lambda
; let ((<var val>) <body>) = ((lambda (<var>) <body>) <val>)
(define (f x y)
    (let 
        (
            (a (+ 1 (* x y)))
            (b (- 1 y))
        )
        (+ (* x a a) (* y b) (* a b))
    )
)
(f 1 1)

; 3 Procedures as General Method (as Procedure Factory) 
; binary search for root finding
; assume f(a)f(b) < 0
(define (bisearch f a b)
    (define (iter a b tol)
        (let ((mid (/ (+ a b) 2)))
            (cond ((< (- b a) tol) mid) 
                ((< (* (f a) (f mid)) 0) (iter a mid tol))
                (else (iter mid b tol))
            )
        )
    )
    (cond ((> a b) (bisearch f b a))
        ((= a b) a)
        (else (iter a b 0.05))
    )
)
(bisearch (lambda (x) (- (* x x) 2)) 0.5 2)

; fixed point finding
(define (fixed_point f x0)
    (define (abs x)
        (if (< x 0)
            (- x)
            x
        )
    )
    (define (iter x tol)
        (let ((next (f x)))
            (if (< (abs (- x next)) tol)
                next
                (iter next tol)
            )
        )
    )
    (iter x0 0.05)
)
(fixed_point cos 1.0)
(fixed_point (lambda (x) (+ (sin x) (cos x))) 1.0)